#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <asm/page.h>
#include <linux/mm.h>
#include <asm/uaccess.h>
#include <linux/fs.h>
#include <linux/of_address.h>
#include <linux/io.h>

#define CVT_BUF_MAX             895
#define KES_MEM_HEADER_LEN      32
#define KES_PEOTECT_LEN         16
#define KES_MAGIC_LEN           8
#define KES_ISENABLE_LEN        8

typedef struct
{
	unsigned char protect[KES_PEOTECT_LEN];
	unsigned char magic[KES_MAGIC_LEN];
	unsigned char isenable[KES_ISENABLE_LEN];
} kes_mem_header_type;

#define KES_TRAPS_BLOCK_SIZE    131072   // 128 * 1024
#define KES_DEBUG_BLOCK_SIZE    393216   // 384 * 1024
#define KES_DMSG_BLOCK_SIZE     524288   // 512 * 1024
#define KES_SYSLOG_BLOCK_SIZE   1048576  // 1 * 1024 * 1024

static unsigned char *kes_syslog_addr;
static unsigned int kes_syslog_offset = KES_MEM_HEADER_LEN;
static kes_mem_header_type *kes_syslog_header;
static DEFINE_SPINLOCK(syslog_lock);

#define KES_SYSLOG_NAME         "kes_syslog_zc"
#define KES_MEM_SHOW_LEN        PAGE_SIZE
#define syslog_page_count       (KES_SYSLOG_BLOCK_SIZE  / KES_MEM_SHOW_LEN)

#define MEM_ISOLATION_SIZE      1024
#define HINT_BUF                "\n--DAWN---\n"
#define HINT_LEN                12

static int print_msg_to_kes_syslog(const char *msg, size_t count)
{
#define SAFE_SYSLOG_SIZE  (KES_SYSLOG_BLOCK_SIZE - MEM_ISOLATION_SIZE)
	static int is_first = 1;

	pr_err("kes ==> start print_msg_to_kes_syslog\n");

	if (!msg || count < 1)
		return 0;

	if (is_first) {
		is_first = 0;
		pr_err("kes ----> print_msg_to_kes_syslog is_first = 0 \n");
		/* initialize the kes syslog mem and fill the header */
		memset(kes_syslog_addr, 0, KES_SYSLOG_BLOCK_SIZE);
		pr_err("kes ----> print_msg_to_kes_syslog memset 0 \n");
		memcpy(kes_syslog_header->isenable, "enable0", KES_ISENABLE_LEN);
	}

	if (count + kes_syslog_offset + HINT_LEN >= SAFE_SYSLOG_SIZE) {
		/* Erase the hint before moving to the start */
		memset(kes_syslog_addr + kes_syslog_offset, 0, HINT_LEN);

		pr_err("kes_syslog reaches end, back to the start!\n");
		kes_syslog_offset = KES_MEM_HEADER_LEN;
	}

	memcpy(kes_syslog_addr + kes_syslog_offset, msg, count);
	kes_syslog_offset += count;

	/* Add hint to the tail of latest msg and no need to update offset */
	memcpy(kes_syslog_addr + kes_syslog_offset, HINT_BUF, HINT_LEN);

	return count;
}

bool end = 0;

static void *kes_syslog_start(struct seq_file *seq, loff_t *pos)
{
	unsigned char *start = (unsigned char *)kes_syslog_addr;

	pr_err("kes ----> start kes_syslog_start\n");

	if (end == 1) {
		return NULL;
	}

	if (*pos >= syslog_page_count) {
		*pos = 0;
		end = 1;
		return NULL;
	}

	return (void *)(start + (*pos) * KES_MEM_SHOW_LEN);
}

static void *kes_syslog_next(struct seq_file *seq, void *v, loff_t *pos)
{
	void *ptr_next = NULL;

	pr_err("kes ----> start kes_syslog_next [%lld] [%ld]\n", (*pos), syslog_page_count);

	ptr_next = (void *)((unsigned char *)v + KES_MEM_SHOW_LEN);

	(*pos)++;
	if (*pos >= syslog_page_count) {
		*pos = 0;
		end = 1;
		return NULL;
	}

	return ptr_next;
}

static int kes_syslog_show(struct seq_file *seq, void *v)
{
	int i = 0;
	void *pt = v;

	pr_err("kes ----> start kes_syslog_show [%p]\n", pt);

	if (pt == NULL) {
		pr_err("kes_debug_mem show data pointer NULL.\n");
		return -1;
	}

	for (i = 0; i < KES_MEM_SHOW_LEN; i++)
		seq_printf(seq, "%c", *((unsigned char *)pt + i));

	return 0;
}

static void kes_syslog_stop(struct seq_file *seq, void *v)
{
	pr_err("kes ----> start kes_syslog_stop [%p]\n", v);
}

const struct seq_operations kes_syslog_seq_ops = {
	.start = kes_syslog_start,
	.next = kes_syslog_next,
	.show = kes_syslog_show,
	.stop = kes_syslog_stop,
};

static int kes_syslog_proc_open(struct inode *inode, struct file *file)
{
	int retval = -1;

	pr_err("kes ==> start kes_syslog_proc_open\n");

	if (file == NULL) {
		pr_err("kes file pointer is NULL.\n");
		return retval;
	}

	end = 0;

	retval = seq_open(file, &kes_syslog_seq_ops);
	if (retval) {
		pr_err("kes cannot open syslog seq_file.\n");
		remove_proc_entry(KES_SYSLOG_NAME, NULL);
	}

	return retval;
}

static ssize_t kes_syslog_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
	char msg[CVT_BUF_MAX + 1] = { 0 };
	ssize_t msg_size = 0;

    pr_err("kes ==> start kes_syslog_write\n");

	if (count > CVT_BUF_MAX || count < 1)
		return -EFAULT;

	if (copy_from_user(msg, buf, count))
		return -EFAULT;

	/* Critical section: syslog write */
	spin_lock(&syslog_lock);
	msg_size = print_msg_to_kes_syslog(msg, count);
	spin_unlock(&syslog_lock);
	return msg_size;
}

static const struct file_operations kes_syslog_fops = {
	.owner = THIS_MODULE,
	.open = kes_syslog_proc_open,
	.read = seq_read,
	.write = kes_syslog_write,
	.llseek = seq_lseek,
	.release = seq_release_private,
};

int kes_syslog_init(void *kes_mem_addr_l)
{
	struct proc_dir_entry *kes_syslog_entry = NULL;

	kes_syslog_addr = (unsigned char *)kes_mem_addr_l + KES_TRAPS_BLOCK_SIZE + KES_DEBUG_BLOCK_SIZE + KES_DMSG_BLOCK_SIZE;
	kes_syslog_header = (kes_mem_header_type *) kes_syslog_addr;

	pr_info("kes_syslog_addr = 0x%p, size = %dKB\n", kes_syslog_addr, (KES_SYSLOG_BLOCK_SIZE / 1024));

	/* create kes syslog proc file system */
	kes_syslog_entry = proc_create(KES_SYSLOG_NAME, 0666, NULL, &kes_syslog_fops);
	if (!kes_syslog_entry) {
		pr_err("kes create %s error.\n", KES_SYSLOG_NAME);
	}

	return 0;
}

void *kes_mem_addr_get(long unsigned int kes_io_mem_addr_l,long unsigned int kes_io_mem_size_l)
{
	void *vtl_addr;

	if(kes_io_mem_addr_l == 0 || kes_io_mem_size_l == 0)
	{
		printk(KERN_ERR "get kes mem addr error\n");
		return NULL;
	}
	vtl_addr = ioremap_wc(kes_io_mem_addr_l, kes_io_mem_size_l);

	return vtl_addr;
}

int hello_init(void)
{
	long unsigned int kes_io_mem_addr_l = 0;
	long unsigned int kes_io_mem_size_l = 0;
	void *kes_vtl_start_addr = 0;

	int ret = 0;
	struct device_node *dev_node=NULL;
	unsigned int registerdetails[4] = {0};
	int reserved_addr_cell;
	int reserved_size_cell;

	printk("kes Hello World!\n");

	dev_node = of_find_node_by_name (NULL, "kes");

	if (dev_node) {
		reserved_addr_cell = of_n_addr_cells(dev_node);
		reserved_size_cell = of_n_size_cells(dev_node);

		printk(KERN_ERR " reserved_size_cell = %d reserved_addr_cell = %d\n",reserved_size_cell,reserved_addr_cell);
		if (reserved_addr_cell == 2 && reserved_size_cell == 2) {
			ret = of_property_read_u32_array(dev_node,
								"reg",
								&registerdetails[0],
								4);
			if (!ret) {
				kes_io_mem_addr_l = registerdetails[1];
				kes_io_mem_size_l = registerdetails[3];

			}
		} else if (reserved_addr_cell == 1 && reserved_size_cell == 1) {
			ret = of_property_read_u32_array(dev_node,
								"reg",
								&registerdetails[0],
								2);
			if (!ret) {
				kes_io_mem_addr_l = registerdetails[0];
				kes_io_mem_size_l = registerdetails[1];
			}
		} else {
			printk(KERN_ERR "Error retrieving reg entry in wigig_dump\n");
		}
	}

	kes_vtl_start_addr = kes_mem_addr_get(kes_io_mem_addr_l, kes_io_mem_size_l);

	pr_info("kes: physical phy addr = 0x%lx, virtual phy addr = 0x%p, size = %luM\n",
			kes_io_mem_addr_l, kes_vtl_start_addr, (kes_io_mem_size_l >> 20));

	if(kes_syslog_init(kes_vtl_start_addr) != 0)
	{
		printk("kes syslog init failed!\n");
		return -1;
	}

	return 0;
}
 
void hello_exit(void)
{
	remove_proc_entry(KES_SYSLOG_NAME, NULL);
	printk("Exit!\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("<libing@autelan.com>");
MODULE_DESCRIPTION("Kernel Exception Saver Module");

